
--[[
C To All programming Language(lua) bind
]]


function split(s,d)
    local t = {};
    local p = "(.-)"..d;
    s = s .. d;
    for match in s:gmatch(p) do
        table.insert(t,match);
    end
    return t;
end

function string.trim(s)
    return (s:gsub("^%s*(.-)%s*$", "%1"))
end

function parseField(s)
    s = s .. " "; --"type: @30     int: 0" \x00 
    local p = "([^ ]-.):(.-[^ ]) ";
    local t = {};
    for m,b in s:gmatch(p) do
        --table.insert(t,match);
        t[m:trim()] = b:trim();
    end
    return t;
end

function_decl = {};
enumeral_type = {};
record_type = {};
identifier_node = {};
type_decl = {};
union_type = {};
tree = {}

function parse(code)
    local array = split(code,"\n@");
    tree = {};
    local nodeMeta = NewNodeMetatable();
    --print(#array);
    for k,v in pairs(array)do
        local offset = v:find(" ");
        local id;
        if(k~=1)then
            id = "@" .. v:sub(1,offset-1);
        else
            id = v:sub(1,offset-1);
        end
        local typ = v:find("[^ ]",offset);
        local field = v:find(" ",typ);
        typ = v:sub(typ,field-1);
        local s = v:sub(field);
        field = parseField(s);
        field.code = typ;
        local t = field;
        if(t.code=="identifier_node")then
            if(t.note)then
                --c++ 
            else
                --print(id);
                local l = tonumber(t.lngt);
                if(l~=#t.strg)then
                    local off = s:find("strg: ");
                    t.strg = s:sub(off + 6,off + 6 + l - 1);
                end
            end
        end
        field.id = id;
        field.root = tree;
        tree[id] = field;
        setmetatable(field, nodeMeta);
    end
    for k,v in pairs(tree) do
        local t = _G[v.code];
        if(t)then
            --t[k] = tree[k];
            table.insert(t,tree[k]);
        end
    end
    --test  
    -- for k,v in pairs(function_decl) do
        -- if(getName(v)=="mmmax")then
            -- _debug = 1;
            -- local prms = getPrmsName(v);
            -- _debug = nil;
            -- local function getPrmsStr(t)
                -- local x = "";
                -- for k,v in ipairs(t) do
                    -- if(x=="")then
                        -- x = x .. v;
                    -- else
                        -- x = x .. "," .. v;
                    -- end
                -- end
                -- return x;
            -- end
            -- local name = getName(v);
            -- print(getRetnTypeName(v),name,"("..getPrmsStr(prms)..");");
        -- end
    -- end
    --print(tree["@1"].code);
end

function getName(t)
    local tree = t.root;
    return tree[t.name].strg;
end

function getTypeName(t)
    if(_debug)then
        print(t.id);
    end
    local tree = t.root;
    
    --typedef xxx xxxx;
    while(true)do
        if(t.code=="record_type")then
            break;
        end
        if(t.unql)then
            t = tree[t.unql];
        else
            break;
        end
    end
    
    if(t.name==nil)then
        return t.code;
    end
    local name = tree[t.name];
    if(name.code=="type_decl")then
        name = tree[name.name];
    end
    return name.strg;
end

function getRetnTypeName(t)
    local tree = t.root;
    local ftype = tree[t.type];
    local retn = tree[ftype.retn];
    return getTypeName(retn);
end

function getRetnTypeCode(t)
    local tree = t.root;
    local ftype = tree[t.type];
    local retn = tree[ftype.retn];
    return retn.code;
end

function getRetnType(t)
    local tree = t.root;
    local ftype = tree[t.type];
    local retn = tree[ftype.retn];
    return retn;
end

function getPrms(t)
    local tree = t.root;
    local ftype = tree[t.type];
    if(ftype.prms==nil)then
        return {};
    end
    local prms = tree[ftype.prms];
    local p = {};
    local next = prms;
    while(true)do
        local typ = tree[next.valu];
        if(next.chan)then
            --local t = getTypeName(typ);  --Name
            local t = typ;
            table.insert(p,t);
        else
            table.insert(p,typ);
            break;
        end
        next = tree[next.chan];
    end
    return p;
end

function getPrmsName(t)
    local prms = getPrms(t);
    local tt = {};
    for k,v in ipairs(prms)do
        local t = getTypeName(v);
        table.insert(tt,t);
    end
    return tt;
end


lua_dump = {};

lua_dump["integer_type"] = function(out,typ,decl,prms_idx)
    local s;
    if(prms_idx>0)then
       s = string.format([[
  int a%d = (int)luaL_checkinteger(L,%d);
]]
    ,prms_idx,prms_idx
    );
    else
        s = "  lua_pushinteger(L,ret);\n  return 1;\n"
    end
    out:write(s);
end

lua_dump["pointer_type"] = function(out,typ,decl,prms_idx)
    local s;
    if(prms_idx>0)then
        s = string.format([[
  void* a%d = 0;
  if(lua_isstring(L,%d)){
    a%d = (void*)lua_tostring(L,%d);
  }
  else if(lua_islightuserdata(L,%d)/*||lua_isuserdata(L,%d)*/){
    a%d = (void*)lua_touserdata(L,%d);
  }
  else if(lua_isuserdata(L,%d)){
    a%d = *(void**)lua_touserdata(L,%d);
  }
  else{
    lua_pushliteral(L,"type error:pointer_type");
    lua_error(L);
  }
]],
    prms_idx,prms_idx,prms_idx,prms_idx,prms_idx,prms_idx,prms_idx,prms_idx,prms_idx,prms_idx,prms_idx
);
        s = string.format([[
  void* a%d = lcheck_toptr(L,%d);
]],
        prms_idx,prms_idx);
    else
        s = "  lua_pushlightuserdata(L,ret);\n  return 1;\n";
    end
    out:write(s);
end

lua_dump["record_type"] = function(out,typ,decl,prms_idx)
    --print("not support record_type");
    --assert(nil);
    local s = '  printf("unsupported type");  assert(0);';
    out:write(s);
    return false;
end

lua_dump["real_type"] = function(out,typ,decl,prms_idx)
    local s;
    if(prms_idx>0)then
       s = string.format([[
  double a%d = luaL_checknumber(L,%d);
]]
    ,prms_idx,prms_idx
    );
    else
        s = "  lua_pushnumber(L,ret);\n  return 1;\n"
    end
    out:write(s);
end

lua_dump["void_type"] = function(out,typ,decl,prms_idx)
    if(prms_idx<1)then 
      out:write("  return 0;\n");
    end 
end

lua_dump["enumeral_type"] = lua_dump["integer_type"];
lua_dump["boolean_type"] = lua_dump["integer_type"];

function lua_dumpf(out,code,typ,decl,prms_idx)
    if(lua_dump[code])then
        return lua_dump[code](out,typ,decl,prms_idx);
    else
        if(code=="complex_type")then
            out:write([[
  lua_pushstring(L,"no support complex_type\n");
  lua_error(L);
  return 0;
]]          );
            return;
        end
        print("dumpf not support type "..code);
        assert(nil);
    end
end

function getLuaRetType(v)
    local t = {
    integer_type = "int",
    pointer_type = "void*",
    real_type = "double",
    void_type = "void",
    enumeral_type = "int",
    record_type = "",
    boolean_type = "int",
    };
    if(t[v])then
        return t[v];
    else
        if(v=="complex_type")then
            return "";
        end
        print("not support type " .. v);
        assert(nil);
    end 
end

require("c2alenum");
require("c2alstruct");

function getDumpFunctions(f,dumpfunc)
    local functions = {};
    local len = #f;
    for k,v in ipairs(function_decl) do
        if(f==nil and dumpfunc==nil)then
            table.insert(functions,v);
        else
           local name = getName(v);
           if(f ==name:sub(1,len) or isDump(name,v))then
                table.insert(functions,v);
           end
        end
    end
    table.sort(functions,function(a,b)
        a = getName(a);
        b = getName(b);
        return a<b;
    end
    );
    return functions;
end

function dumpFunctions2lua(f,out)
    local dumpAll = false;
    local len;
    local dump_list = {};
    if(out==nil)then
        out = io.stderr;
    elseif(type(out)=="string")then --如果是字符串 则，打开out文件  FILE*
        out = io.open(out,"w+");
    end
    local functions = getDumpFunctions(f,isDump);
    
    local s = [[
#include <lua.h>
#include <lualib.h>
#include <lauxlib.h>
#include <assert.h>


#define lua_isstring(a,idx) (lua_type(a,idx)==LUA_TSTRING)
#ifndef lcheck_toptr
#define lcheck_toptr _lcheck_toptr
static void* _lcheck_toptr(lua_State*L,int idx){
  void* data = 0;
  if(lua_isstring(L,idx)){
    data = (void*)lua_tostring(L,idx);
  }
  else if(lua_islightuserdata(L,idx)/*||lua_isuserdata(L,idx)*/){
    data = (void*)lua_touserdata(L,idx);
  }
  else if(lua_isuserdata(L,idx)){
    data = *(void**)lua_touserdata(L,idx);
  }
  else{
    lua_pushliteral(L,"type error:pointer_type");
    lua_error(L);
  };
  return data;
};
#endif

]];
    out:write(s);

    nolink_file = io.tmpfile();
    if(nolink and outfilename)then
    s=[[
#if defined(NOLINK)
#include <%s>
#endif

]];
        s = string.format(s,outfilename..".h");
        out:write(s);
    end

    for k,v in ipairs(functions) do
        local dump = false;
        local name = getName(v);
        --if(dumpAll or isDump(name,v) or name:sub(1,len)==f)then
            local ret = getRetnTypeCode(v);
            local prms = getPrms(v);
            local nocall =false;
            out:write(string.format("static int l%s(lua_State*L){\n",name));
            
            for kk,vv in ipairs(prms) do
                if(vv.code=="void_type")then
                    break;
                end
                if(vv.code=="complex_type")then
                    nocall = true;
                elseif(lua_dumpf(out,vv.code,vv,v,kk)==false)then
                    nocall = true;
                end
                --print(vv.code);
            end
            local prms_str = "";
            for i=1,#prms do
                if(prms[i].code=="void_type")then
                    break;
                end
                if(i==1)then
                    prms_str = prms_str .. "a" .. i;
                else
                    prms_str = prms_str .. ",a" .. i;
                end
            end
            local rname = getLuaRetType(ret);
            local s;
            if(rname=="void" or rname=="")then
                s = string.format("  %s(%s);\n",name,prms_str);
            else
                s = string.format("  %s ret = (%s)%s(%s);\n",rname,rname,name,prms_str);
            end
            local ss = string.format("DEFCF(%s,%s);\n",rname,name);
            nolink_file:write(ss);
            if(nocall)then
                if(rname~="void" and rname~="")then
                    s = string.format("  %s ret = 0;\n",rname);
                else
                    s = "/* no support argument type*/\n";
                end 
            end
            out:write(s);
            lua_dumpf(out,ret,ret,v,0);
            
            out:write("};\n\n");
            table.insert(dump_list,name);
        --end
    end
    local s;

    if(load_dll_func)then
        s = [[
static int l%sLoad(lua_State*L){
    void*dll;
    int ret;
    if(lua_isstring(L,1)){
        dll = dlopen(lua_tostring(L,));
    }
    else{
        dll = lcheck_toptr(L,1);
    }
    ret = lua_loadsymstoptr(L,dll,&(%sfuncs));
    lua_pushinteger(L,ret);
    return 1;
}
]];
        s = string.format(s,lib_name,lib_name);
        out:write(s);
    end

    if(lib_name)then
        s = string.format("int %s_openlibs(lua_State*L){\n",lib_name);
    else
        s = string.format("int %s_openlibs(lua_State*L){\n",f);
    end
    out:write(s);


    if(load_dll_func)then
        s = [[
  lua_register(L,"%sLoad",l%sLoad);
  lua_loadsyms(L,"%s",);
]]
        s = string.format(s,lib_name,lib_name);
        out:write(s);
    end

  --开启一些东西
  if(false)then
--
    -- s = [[
-- #if defined(LUA_RETURN_TABLE)
  -- int ref = LUA_REFNIL;
  -- int type;
  -- const char*name = "%s";
  -- lua_getglobal(L,name);
  -- type = lua_type(L,-1);
  -- if((type!=LUA_TTABLE)&&(type!=LUA_TNIL)){
    -- lua_createtable(L,0x300,0);
    -- ref = luaL_ref(L,LUA_REGISTRYINDEX);
    -- lua_rawgeti(L,LUA_REGISTRYINDEX,ref);
  -- }
  -- else{
    -- lua_createtable(L,0x300,0);
    -- lua_setglobal(L,name);
    -- lua_getglobal(L,name);
  -- }
-- #undef  lua_register
-- #define lua_register(L,n,f) lua_pushcfunction(L,f); lua_setfield(L,-2,n); lua_pushcfunction(L,f); lua_setglobal(L,n)
-- #endif
-- ]]
    -- s = string.format(s,lib_name)

    else
    s = [[
#if defined(LUA_RETURN_TABLE)

#else

#endif
]]

    end;
    
    if(nolink==nil or nolink==false)then
        s = [[
  const char*name = "%s";
#ifndef lregister
//1
#define lregister(L,n) lua_register(L,#n,l##n)
]];
    else
        s = [[
  const char*name = "%s";
#ifndef lregister
//2
  int ref = LUA_REFNIL;
  lua_newtable(L);
  lua_setglobal(L,name);
  lua_getglobal(L,name);
  ref = luaL_ref(L,LUA_REGISTRYINDEX);
#if defined(NOLINK)
  int funcref = LUA_REFNIL;
  lua_newtable(L);
  funcref = luaL_ref(L,LUA_REGISTRYINDEX);
  lua_setfield(L,-2,"__dll");
 #define setfuncref(L,n) \
    lua_rawgeti(L,LUA_REGISTRYINDEX,funcref);           \
    lua_pushlightuserdata(L,&n); lua_setfield(L,-2,#n); 


#else //!defined(NOLINK)
 #define setfuncref(...)
#endif


#define lregister(L,n)    \
    lua_pushcfunction(L,l##n);lua_setglobal(L,#n);     \
    lua_rawgeti(L,LUA_REGISTRYINDEX,ref);              \
    lua_pushlightuserdata(L,n);lua_setfield(L,-2,#n);  \
    setfuncref(L,n)

#endif
]]
    end;
    s = string.format(s,lib_name);
    out:write(s);
    
    if(nolink)then
        nolink_file:seek("set");
        s = nolink_file:read("*a");
        nolink_file = io.open(string.format("%s",outfilename..".h"),"w+");
        local head = io.open("dlload.h","r");
        nolink_file:write(head:read("*a"));
        head:close();
        nolink_file:write(s);
        
        for k,v in ipairs(dump_list) do
            s = string.format('#define %s c%s\n',v,v);
            nolink_file:write(s);
        end
        
    end
    
    

    for k,v in ipairs(dump_list) do
        s = string.format('  lregister(L,%s);\n',v);
        out:write(s);
    end

    for k,v in ipairs(dump_list)do

    end

    if(enable_enum)then
        out:write("\n\n//enum \n");
        dumpEnums2lua(f,out);
    end
    if(enable_struct)then
        out:write("\n\n//struct \n");
        dumpStructs2lua(f,out);
    end

    if(false)then
        s = [[
#if defined(LUA_RETURN_TABLE)
  if(ref==LUA_REFNIL){
    lua_getglobal(L,name);
    return 1;
  }
  else{
    lua_rawgeti(L,LUA_REGISTRYINDEX,ref);
    luaL_unref(L,LUA_REGISTRYINDEX,ref);
    return 1;
  }
#else
  return 0;
#endif
]]
        out:write(s);
    end

    out:write("  return 0;\n\n};\n\n");
    if(lib_name)then
        out:write(string.format("int luaopen_%s(lua_State*L){\n",lib_name));
        s = string.format("  return %s_openlibs(L);\n",lib_name);
    else
        out:write(string.format("int luaopen_%s(lua_State*L){\n",f));
        s = string.format("  return %s_openlibs(L);\n",f);
    end
    out:write(s);
    out:write("};\n\n");
end

outfile = io.stdout;
outfilename = nil;
inputfile = nil;
filter = nil;
enable_struct = false;
enable_enum = true;
load_files = {};
do_files = {};
out_debug = false;
lib_name = nil;
isDump = function(name,decl) return false; end;

local k = 0;
while(k<#arg) do
    k = k + 1;
    local v = arg[k];
    if(v:sub(1,1)~="-")then
        inputfile = v;
    else 
        if(v=="-o" or v:sub(1,2)=="-o")then
            --out file
            local out;
            if(v=="-o")then
                out = arg[k+1];
                k = k + 1;
            else
                out = v:sub(3);
            end
            outfilename = out;
            outfile = io.open(out,"w+");
        elseif(v=="-l")then
            --load lua file
            --设置一些选项接下来导出数据
            --和-d 选项不同
            table.insert(load_files,arg[k+1]);
            k = k + 1;
        elseif(v=="-d")then
            --do lua file
            --执行lua脚本, 不会去生成数据
            --可以调用函数来生成数据
            table.insert(do_files,arg[k+1]);
            k = k + 1;
        elseif(v=="-f")then
            --filter
            filter = arg[k+1];
            k = k + 1;
        elseif(v=="-es")then
            --enable struct out
            enable_struct = true;
        elseif(v=="-de")then
            enable_enum = false;
        elseif(v=="-ed")then
            --enable out debug
            out_debug = true;
        elseif(v=="-name")then
            lib_name = arg[k+1];
            k = k + 1;
        elseif(v=="-dllname")then
            dll_name = arg[k+1];
            k = k + 1;
        elseif(v=="-lang")then
            langdump = arg[k+1];
            k = k + 1;
        elseif(v=="-sed")then
            sortEnumDump = true;
        elseif(v=="-nolink")then
            nolink = true;
        end
    end
end

print("input  :",inputfile);
print("out    :",outfile);
print("filter :",filter);
if(lib_name)then
print("libName:",lib_name);
end
if(langdump)then
print("lang   :",langdump);
end
if(enable_struct)then
print("struct : true");
end
local file = io.open(inputfile,"r");
local code = file:read("*a");
parse(code);

for k,v in ipairs(load_files) do
    dofile(v);
end

if(#do_files>0)then
    for k,v in ipairs(do_files)do
        dofile(v);
    end;
else
    if(langdump)then
        dofile("lang/"..langdump..".lua");
        ldump_function(filter,outfile);
    else
        dumpFunctions2lua(filter,outfile);
    end
end
--dumpEnums2lua(arg[3]);

